﻿using CredProvider.NET.Interop2;
using System.Drawing;
using System.Runtime.InteropServices;
using static CredProvider.NET.Constants;

namespace CredProvider.NET
{
    public class CredentialProviderCredential : ICredentialProviderCredential2
    {
        private readonly CredentialView view;
        private string sid;

        public CredentialProviderCredential(CredentialView view, string sid)
        {
            Logger.Write();

            this.view = view;
            this.sid = sid;
        }

        /// <summary>
        /// 它用于向 Windows 凭据管理器注册凭据提供程序的回调函数。凭据提供程序可以使用 Advise 函数来注册一个回调函数，该回调函数将在 Windows 凭据管理器需要凭据提供程序执行某些操作时被调用。
        /// 例如，当用户选择凭据提供程序时，Windows 凭据管理器将调用凭据提供程序的回调函数来请求凭据。
        ///Advise 函数的参数包括要注册的回调函数、回调函数的上下文和一个指向 DWORD 类型的指针。如果函数成功，则返回 S_OK。否则，函数将返回一个错误代码，您可以使用 HRESULT 宏来解释该代码。
        /// </summary>
        /// <param name="pcpce"></param>
        /// <returns></returns>
        public virtual int Advise(ICredentialProviderCredentialEvents pcpce)
        {
            Logger.Write();

            if (pcpce is ICredentialProviderCredentialEvents2 ev2)
            {
                Logger.Write("pcpce is ICredentialProviderCredentialEvents2");
            }

            return HRESULT.S_OK;
        }

        public virtual int UnAdvise()
        {
            Logger.Write();

            return HRESULT.E_NOTIMPL;
        }


        /// <summary>
        /// 它用于向 Windows 凭据管理器报告凭据提供程序的选定状态。凭据提供程序可以使用 SetSelected 函数来向 Windows 凭据管理器报告其是否被选定。如果凭据提供程序被选定，则 Windows 凭据管理器将在用户登录时使用该凭据提供程序来验证用户的凭据。
        ///SetSelected 函数的参数是一个布尔值，它指定凭据提供程序是否被选定。如果函数成功，则返回 S_OK。否则，函数将返回一个错误代码，您可以使用 HRESULT 宏来解释该代码。
        /// </summary>
        /// <param name="pbAutoLogon"></param>
        /// <returns></returns>
        public virtual int SetSelected(out int pbAutoLogon)
        {
            Logger.Write();

            //如果您希望在选择时立即调用 GetSerialization，请将此项设置为 1.是否自动登录，0是否，1是是
            pbAutoLogon = 0;

            return HRESULT.S_OK;
        }

        public virtual int SetDeselected()
        {
            Logger.Write();

            return HRESULT.E_NOTIMPL;
        }

        public virtual int GetFieldState(
            uint dwFieldID,
            out _CREDENTIAL_PROVIDER_FIELD_STATE pcpfs,
            out _CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE pcpfis
        )
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            view.GetFieldState((int)dwFieldID, out pcpfs, out pcpfis);

            return HRESULT.S_OK;
        }

        public virtual int GetStringValue(uint dwFieldID, out string ppsz)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            ppsz = view.GetValue((int)dwFieldID);
            
            return HRESULT.S_OK;
        }

        private Bitmap? tileIcon = null;

        public virtual int GetBitmapValue(uint dwFieldID, out IntPtr phbmp)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            try
            {
                TryLoadUserIcon();
            }
            catch (Exception ex) 
            {
                Logger.Write("Error: " + ex);
            }

            phbmp = tileIcon?.GetHbitmap() ?? IntPtr.Zero;

            return HRESULT.S_OK;
        }

        private void TryLoadUserIcon()
        {
            Logger.Write();

            if (tileIcon == null)
            {
                // This needs to match your final installation destination.
                // For testing purposes, it's easiest to create that destination file manually.
                string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +"\\tile-icon.bmp";
                Logger.Write("path: " + path);
                tileIcon = new Bitmap(path);
            }
        }

        public virtual int GetCheckboxValue(uint dwFieldID, out int pbChecked, out string ppszLabel)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            pbChecked = 0;
            ppszLabel = "";

            return HRESULT.E_NOTIMPL;
        }

        public virtual int GetSubmitButtonValue(uint dwFieldID, out uint pdwAdjacentTo)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            pdwAdjacentTo = 0;

            return HRESULT.E_NOTIMPL;
        }

        public virtual int GetComboBoxValueCount(uint dwFieldID, out uint pcItems, out uint pdwSelectedItem)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            pcItems = 0;
            pdwSelectedItem = 0;

            return HRESULT.E_NOTIMPL;
        }

        public virtual int GetComboBoxValueAt(uint dwFieldID, uint dwItem, out string ppszItem)
        {
            Logger.Write($"dwFieldID: {dwFieldID}; dwItem: {dwItem}");

            ppszItem = "";

            return HRESULT.E_NOTIMPL;
        }

        public virtual int SetStringValue(uint dwFieldID, string psz)
        {
            Logger.Write($"dwFieldID: {dwFieldID}; psz: {psz}");

            view.SetValue((int) dwFieldID, psz);

            return HRESULT.S_OK;
        }

        public virtual int SetCheckboxValue(uint dwFieldID, int bChecked)
        {
            Logger.Write($"dwFieldID: {dwFieldID}; bChecked: {bChecked}");

            return HRESULT.E_NOTIMPL;
        }

        public virtual int SetComboBoxSelectedValue(uint dwFieldID, uint dwSelectedItem)
        {
            Logger.Write($"dwFieldID: {dwFieldID}; dwSelectedItem: {dwSelectedItem}");

            return HRESULT.E_NOTIMPL;
        }

        public virtual int CommandLinkClicked(uint dwFieldID)
        {
            Logger.Write($"dwFieldID: {dwFieldID}");

            return HRESULT.E_NOTIMPL;
        }

        public virtual int GetSerialization(
            out _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE pcpgsr,
            out _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION pcpcs,
            out string ppszOptionalStatusText,
            out _CREDENTIAL_PROVIDER_STATUS_ICON pcpsiOptionalStatusIcon
        )
        {
            Logger.Write("获取序列化凭据开始");

            var usage = this.view.Provider.GetUsage();

            pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_NO_CREDENTIAL_NOT_FINISHED;
            pcpcs = new _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();
            ppszOptionalStatusText = "";
            pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_NONE;

            //可以在用户输入任何值之前调用序列化。仅适用于登录使用场景
            if (usage == _CREDENTIAL_PROVIDER_USAGE_SCENARIO.CPUS_LOGON || usage == _CREDENTIAL_PROVIDER_USAGE_SCENARIO.CPUS_UNLOCK_WORKSTATION)
            {
                //确定身份验证包
                Common.RetrieveNegotiateAuthPackage(out var authPackage);

                //使用此代码仅支持msv1_0的凭据打包
                Logger.Write($"获得身份验证包: {authPackage}. 仅支持本地身份验证包 0 （msv1_0.");

                //获取用户名和密码
                var username = Common.GetNameFromSid(this.sid);
                GetStringValue(2, out var password);

                bool result = false;
                DriveInfo[] allDrives = DriveInfo.GetDrives();
                foreach (DriveInfo d in allDrives)
                {
                    if (d.Name == "G:\\")
                    {
                        Logger.Write("U 盘已插入计算机。");
                        result = true;
                    }
                }

                Logger.Write($"正在准备使用密码序列化凭据...，用户名：{username}，密码：{password}");

                password = result?"123456":"67888";
                //这里可以调用摄像头实现人脸检测或者读取移动U盘密钥

                pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_RETURN_CREDENTIAL_FINISHED;
                pcpcs = new _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();

                var inCredSize = 0;
                var inCredBuffer = Marshal.AllocCoTaskMem(0);
                //这应该在仅使用登录方案的 Windows 10 中正常工作
                //但是在旧操作系统上解锁scanario的工作站可能会失败
                if (!PInvoke.CredPackAuthenticationBuffer(0, username, password, inCredBuffer, ref inCredSize))
                {
                    Marshal.FreeCoTaskMem(inCredBuffer);
                    inCredBuffer = Marshal.AllocCoTaskMem(inCredSize);
                    if (PInvoke.CredPackAuthenticationBuffer(0, username, password, inCredBuffer, ref inCredSize))
                    {
                        ppszOptionalStatusText = string.Empty;
                        pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_SUCCESS;

                        //最好将 CLSID 移动到常量（但当前在 .reg 文件中使用）
                        pcpcs.clsidCredentialProvider = Guid.Parse("00006d50-0000-0000-b090-00006b0b0000");
                        pcpcs.rgbSerialization = inCredBuffer;
                        pcpcs.cbSerialization = (uint)inCredSize;
                        pcpcs.ulAuthenticationPackage = authPackage;
                        Logger.Write("打包凭据成功");
                        return HRESULT.S_OK;
                    }
                    Logger.Write("无法打包凭据");
                    ppszOptionalStatusText = "无法打包凭据";
                    pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_ERROR;
                    return HRESULT.E_FAIL;
                }
            }
            //在此处实现更改密码的代码。这不是本机处理的。
            else if (usage == _CREDENTIAL_PROVIDER_USAGE_SCENARIO.CPUS_CHANGE_PASSWORD)
            {
                pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_NO_CREDENTIAL_FINISHED;
                pcpcs = new _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();
                ppszOptionalStatusText = "Password changed success message.";
                pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_SUCCESS;
            }

            Logger.Write("返回 S_OK");
            return HRESULT.S_OK;
        }

        /// <summary>
        /// 用于向凭据提供程序引擎报告凭据提供程序的状态。在实现 ICredentialProviderCredential 接口时，您需要使用 ReportResult 方法来报告凭据提供程序的状态，并告诉凭据提供程序引擎下一步该做什么。以下是 ReportResult 方法的一些常见用法：
        /// 如果凭据提供程序成功验证了用户的凭据，则可以使用 ReportResult 方法来报告 CPNAP_SUCCESS 状态，并告诉凭据提供程序引擎继续处理用户的登录请求。
        /// 如果凭据提供程序无法验证用户的凭据，则可以使用 ReportResult 方法来报告 CPNAP_FAILURE 状态，并告诉凭据提供程序引擎显示错误消息或提示用户重新输入凭据。
        /// 如果凭据提供程序需要更多的用户输入才能验证凭据，则可以使用 ReportResult 方法来报告 CPNAP_SHOW_HELP 状态，并告诉凭据提供程序引擎显示帮助消息或提示用户输入额外的信息。
        /// </summary>
        /// <param name="ntsStatus"></param>
        /// <param name="ntsSubstatus"></param>
        /// <param name="ppszOptionalStatusText"></param>
        /// <param name="pcpsiOptionalStatusIcon"></param>
        /// <returns></returns>
        public virtual int ReportResult(
            int ntsStatus,
            int ntsSubstatus,
            out string ppszOptionalStatusText,
            out _CREDENTIAL_PROVIDER_STATUS_ICON pcpsiOptionalStatusIcon
        )
        {
            Logger.Write($"ntsStatus: {ntsStatus}; ntsSubstatus: {ntsSubstatus}");

            ppszOptionalStatusText = "请插入U盾后进行登录,哈哈哈";
            pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_NONE;

            return HRESULT.S_OK;
        }

        public virtual int GetUserSid(out string sid)
        {
            Logger.Write();

            sid = this.sid;
            Logger.Write($"返回 sid: {sid}");
            return HRESULT.S_OK;
        }
    }
}